Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(core/entityTag): Refactor Entity Tags to React #3717

Merged

Conversation

christopherthielen
Copy link
Contributor

@christopherthielen christopherthielen commented May 19, 2017

  • Introduce imperative React Modal API
  • Introduce Formsy validation for React

I guess I should fix the tests too

}

@autoBindMethods
class EntityTagMessage extends React.Component<IEntityTagMessageProps, {}> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split these components out, but left them in the same file. Good? Bad? Separate files? The option component is teeny and I didn't think deserved a file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small enough, only used here....

ReactInjector, TaskMonitor, TaskMonitorBuilder
} from 'core';

import { BasicFieldLayout, TextArea, ReactModal } from 'core/presentation';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these imports were imported from 'core' before, and ended up being used while they were still undefined because of timing and dependency loops

entityRef: IEntityRef;
isNew: boolean;
show?: boolean;
onHide?(event: any): void;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do yall like or hate this syntax?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works for me! doesn't look like there are arguments though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller can access the trigger event from the click event handler or whatever. IDK if it's necessary.

I used onHide.apply to propagate any arguments

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

const { isNew, tag, ownerOptions } = this.props;
const message = this.state.message || '';

const closeButton = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may deserve its own component in its own file.

React modal has a closeButton={true} support but it uses stock bootstrap markup, which doesn't look the same. In the future, a PR to react-bootstrap allows a component to be passed like closeButton={DeckCloseButton}

</div>
);

const { TaskMonitorWrapper } = NgReact;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dereference NgReact components inside render to avoid trouble

export interface IState extends IFormComponentState { }

/** A simple Formsy form component for validated <input> tags (text or checkbox) */
export class Input extends FormComponent<string, IProps, IState> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not actually using this component, but this shows a simpler approach which doesn't accept a layout strategy.

*/
@autoBindMethods()
export class TextArea extends FormComponent<string, IProps, IState> {
public static contextTypes = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This grants the TextArea component access to the formsy context object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type your context, then contextTypes has type React.ValidationMap<ITextAreaContext>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your context

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:trollface:

const isInvalid = this.showError() || this.showRequired();
const isDirty = !this.isPristine();
const inputClass = `form-control ${className} ${isInvalid ? 'ng-invalid' : ''} ${isDirty ? 'ng-dirty' : ''}`;
const _input = <textarea className={inputClass} rows={rows} name={name} onChange={this.changeValue} value={this.getValue()} />;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds the actual <textarea> element and wires the data and events

const _error = errorMessage && isDirty && <span className="error-message">{errorMessage}</span>;

const FormFieldLayout = layout;
return (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This delegates to the layout, passing it the 4 elements: label, input, help, error

// Type wrapper for Formsy-React
// Project: https://github.com/christianalfoni/formsy-react

declare module 'formsy-react' {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are no @types/formsy-react, so this is a very incomplete typings file for it

Copy link
Contributor

@anotherchrisberry anotherchrisberry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some generally minor stuff but LGTM overall - very nice work.

private $uibModalInstanceEmulation: IModalServiceInstance & { deferred?: IDeferred<any> };

/** Shows the Entity Tag Editor modal */
public static show(props: IEntityTagEditorProps): Promise<any> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promise<void>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the generic sense, it (Modal.show) actually does resolve to the "onHide action". This could be used to return a promise for a radio choice in a modal, for example.

In this case (EntityTagEditor) it doesn't resolve to anything, so changing to void is proper

isValid: false,
isSubmitting: false,
owner: owner,
entityType: this.props.entityType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to destructuring above

application: application,
title: `${isNew ? 'Create' : 'Update'} ${this.props.tag.value.type} for ${entityRef.entityId}`,
modalInstance: this.$uibModalInstanceEmulation,
onTaskComplete: () => this.props.onUpdate(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destructure?

<button type="submit" className="btn btn-primary" disabled={!this.state.isValid || this.state.isSubmitting}>
<span className="glyphicon glyphicon-ok-circle" />
<span>{this.props.isNew ? ' Create' : ' Update'} {this.props.tag.value.type}</span>
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth replacing with SubmitButton - might not be, what do I know

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting a submit button that's reused elsewhere (code in its own file), or simply extracting it to a const?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there already exists a <SubmitButton> React component in there somewhere...you just have to find it.


@autoBindMethods
class EntityTagMessage extends React.Component<IEntityTagMessageProps, {}> {
private handleTextareaChanged(event: React.FormEvent<any>): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any can probably be HTMLTextAreaElement

public constructor(private $timeout: ng.ITimeoutService, private $uibModal: IModalService,
private confirmationModalService: any, private entityTagWriter: EntityTagWriter) {
public constructor(private $timeout: ng.ITimeoutService,
private confirmationModalService: any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be typed to ConfirmationModalService

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also import ITimeoutService from angular

};

private helpContentsRegistry: HelpContentsRegistry;
private helpContents: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be typed to something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ [key: string]: string } probably

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or the IHelpContents type from below

}
}

public render(): React.ReactElement<any> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove return type

* | | ||
* | +---------------------------------------+|
* +----------------------------------------------------------+
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice ascii art!

@@ -1,3 +1,5 @@
export * from './HoverablePopover';
export * from './Tooltip';
export * from './LabelComponent';
export * from './ModalService';
export * from './formsy/index';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefer export * from './formsy since there's an index in there

Copy link
Contributor

@jrsquared jrsquared left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few questions and style things, but LGTM. great work!

entityRef: IEntityRef;
isNew: boolean;
show?: boolean;
onHide?(event: any): void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure!

</div>
);

const submitButton = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might make sense to use SubmitButton and add support for custom button text to support the {tag.value.type} bit.

return (
<Modal show={this.state.show} onHide={this.onHide} dialogClassName="entity-tag-editor-modal">

<TaskMonitorWrapper monitor={this.state.taskMonitor} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right?!

}

@autoBindMethods
class EntityTagMessage extends React.Component<IEntityTagMessageProps, {}> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small enough, only used here....

<TextArea
label="Message"
_help={<div>Markdown is okay <HelpField id="markdown.examples"/></div>}
layout={BasicFieldLayout}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


import { FormComponent, IFormComponentState, IFormComponentProps } from '../FormComponent';

export interface IProps extends IFormComponentProps {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did we land on exported props names? I like the pattern of if its exported to name it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exported because Typescript requires it, but it is not re-exported from the module index:

export { Input } from './Input';
export { TextArea } from './TextArea';

cool?


public static defaultProps = {
name: null as any,
onChange: () => null as any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the benefit of null as any over {}?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no benefit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've imported noop from core/utils in other projects which I think worked nicely. Then you don't have to keep creating the same () => {} everywhere

*/
@autoBindMethods()
export class TextArea extends FormComponent<string, IProps, IState> {
public static contextTypes = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type your context, then contextTypes has type React.ValidationMap<ITextAreaContext>

formsy: PropTypes.any
};

public static defaultProps = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default props should have a type of Partial<IProps>


/** An interface which Formsy form field layouts should accept as props */
export interface IFormFieldLayoutProps extends HTMLAttributes<any> {
_label?: React.ReactElement<any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so many underscores...

@christopherthielen christopherthielen merged commit e96db46 into spinnaker:master May 19, 2017
chris-h-phillips pushed a commit to chris-h-phillips/deck that referenced this pull request May 25, 2017
- Introduce imperative React Modal API
- Introduce Formsy validation for React
- chore(travis): run yarn
@christopherthielen christopherthielen deleted the edit-entity-tags branch June 2, 2017 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants